// This programm scheduler two LWPs created with pthread.
// Each LWP consists of two user space threads that are independently
// scheduled with the user space scheduler implemented in scheduler.c

// Include pthreads
#include <pthread.h>

// Include scheduling library.
#include "scheduler.h"

// define number of loops for each user space thread
int laps1;
int laps2;


// This function gets the Index of each LWP.
// We need this function for debugging/demonstrastion
// as we want to be able to determine the LWP in
// which the user space thread is running.
int lwpId2Idx(void) {

    // get thread id for LWP
    pthread_t id = pthread_self();
    // initialize index of LWP
    int lwpIdx = -1;
    int i;

    // find LWP index from pthread identification
    for (i=0; i<MAX_LWPS; i++) {
	if ( pthread_equal(lwpIdx2Id[i],id) ) {
	    lwpIdx = i;
	    break;
	}  
    }
    if ( lwpIdx < 0 ) {
	printf("Problem with finding LWP index\n");
	exit(1);
    }

    return lwpIdx;
}

// First user space thread (used once in each LWP).
int utF1(void *arg) {

    int i = 0;
    // arguments: id + data 
    utInputData_t *u = (utInputData_t *)arg;
    // get id and data: this is important, because we need the data on
    // our own stack
    int n = u->utId;
    char *c = strdup(u->data);
    // get index of our LWP
    // this is necessary to get the correct element in the
    // array that manages the user threads in each LWP
    int lwpIdx = lwpId2Idx();

    // loop some time to demonstrate scheduling of user space threads
    // and LWPs
    while ( i++ < laps1 ) {
	//for demonstration and debugging, show:
	//a) id of user space thread
	//b) parameters of user space thread (name of thread)
	//c) current value of i
	//sleep to slow down output
#ifdef _PRINT
	printf("Ut %d %s i = %d\n",n,c,i);
	sleep(1);
#endif
	//after printf give CPU to the next user space thread
	utYield(lwpIdx);
    }

    //after our loop is gone, terminate user space thread
    return 0;
}

// Second user space thread (used once in each LWP).
// Works nearly identical to utF1, just different number
// of loops.
int utF2(void *arg) {

    int i = 0;
    utInputData_t *u = (utInputData_t *)arg;
    int n = u->utId;
    char *c = strdup(u->data);
    int lwpIdx = lwpId2Idx();

    while ( i++ < laps2 ) {
#ifdef _PRINT
	printf("Ut %d %s i = %d\n",n,c,i);
	sleep(1);
#endif
	utYield(lwpIdx);
    }

    return 0;
}

// In our main function, we start two LWPs and two user space threads for
// each LWP. 
int main(int argc, char *argv[]) {

    //define data for user space threads
    
    //stack size for each user space thread
    int skSize = 4096;
    //parameters that are given to thread at startup
    utInputData_t *u;

    //define data for LWPs

    //our two LWPs
    pthread_t thread0;
    pthread_t thread1;

    //arguments for the LWPs
    int *arg0;
    int *arg1;

    // Parse input parameters: argv[1] defines
    // number of cycles to be performed by Thread1,
    // argv[2] the same for Thread2
    if ( argc < 2 ) 
	laps1 = 10;
    else
	laps1 = atoi(argv[1]);

    if ( argc < 3 ) 
	laps2 = laps1;
    else 
	laps2 = atoi(argv[2]);


    // Create a context for each user thread to run
    // on LWP 0
    u = (utInputData_t *)malloc(sizeof(utInputData_t));
    u->utId = 1;
    u->data = "User Thread 1.1";
    utMakeContext(&(utArray[0][utNum[0]++]),utF1,skSize,u);

    u = (utInputData_t *)malloc(sizeof(utInputData_t));
    u->utId = 2;
    u->data = "User Thread 1.2";
    utMakeContext(&(utArray[0][utNum[0]++]),utF2,skSize,u);


    // Create a context for each user thread to run
    // on LWP 1
    u = (utInputData_t *)malloc(sizeof(utInputData_t));
    u->utId = 1;
    u->data = "User Thread 2.1";
    utMakeContext(&(utArray[1][utNum[1]++]),utF1,skSize,u);

    u = (utInputData_t *)malloc(sizeof(utInputData_t));
    u->utId = 2;
    u->data = "User Thread 2.2";
    utMakeContext(&(utArray[1][utNum[1]++]),utF2,skSize,u);


    // Activate 1st LWP thread
    arg0 = (int *)malloc(sizeof(int));
    *arg0 = 0;
    pthread_create(&thread0,NULL,utSchedule,(void *)arg0);

    // Activate 2nd LWP thread
    arg1 = (int *)malloc(sizeof(int));
    *arg1 = 1;
    pthread_create(&thread1,NULL,utSchedule,(void *)arg1);

    // Wait for all LWPs to finish
    pthread_join(thread0,NULL);
    pthread_join(thread1,NULL);
  

    exit(0);

}
